package com.immediatus.util;

import java.util.Collections;
import java.util.Stack;

public abstract class GenericPool<T>{

    private final Stack<T> _availableItems = new Stack<T>();
    private int _unrecycledCount;
    private final int _growth;

    public GenericPool(){
        this(0);
    }

    public GenericPool(final int initialSize_){
        this(initialSize_, 1);
    }

    public GenericPool(final int initialSize_, final int growth_){
        if (growth_ < 0){
            throw new IllegalArgumentException("growth must be at least 0!");
        }

        this._growth = growth_;

        if (initialSize_ > 0){
            this.batchAllocatePoolItems(initialSize_);
        }
    }

    public synchronized int getUnrecycledCount(){
        return this._unrecycledCount;
    }

    protected abstract T onAllocatePoolItem();

    protected void onHandleRecycleItem(final T item_){
    }

    protected T onHandleAllocatePoolItem(){
        return this.onAllocatePoolItem();
    }

    protected void onHandleObtainItem(final T item_){
    }

    public synchronized void batchAllocatePoolItems(final int count_){
        final Stack<T> availableItems = this._availableItems;
        for (int i = count_ - 1; i >= 0; i--){
            availableItems.push(this.onHandleAllocatePoolItem());
        }
    }

    public synchronized T obtainPoolItem(){
        final T item;

        if (this._availableItems.size() > 0){
            item = this._availableItems.pop();
        } else{
            if (this._growth == 1){
                item = this.onHandleAllocatePoolItem();
            } else{
                this.batchAllocatePoolItems(this._growth);
                item = this._availableItems.pop();
            }
        }
        this.onHandleObtainItem(item);

        this._unrecycledCount++;
        return item;
    }

    public synchronized void recyclePoolItem(final T item_){
        if (item_ == null){
            throw new IllegalArgumentException("Cannot recycle null item!");
        }

        this.onHandleRecycleItem(item_);
        this._availableItems.push(item_);
        this._unrecycledCount--;
    }

    public synchronized void shufflePoolItems(){
        Collections.shuffle(this._availableItems);
    }
}
